﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace JustTypeUI.Logic
{
    /// <summary>
    /// Provides parsing for raw user commands
    /// 
    /// Main Author: Xiao Yuguang, Meng Kaizhi
    /// </summary>
    public class CommandParser
    {
        //new command object which will be returned
        private Command command;

        //to store all the chopped components of user command line
        private List<string> commandComponents;

        //string lists for possible prepositions and keywords inputs
        private List<string> locationSet = new List<string> { "AT", "@" };
        private List<string> deadlineSet = new List<string> { "BEFORE", "<", "ON" };
        private List<string> addSet = new List<string> { "ADD", ".A", "+" };
        private List<string> deleteSet = new List<string> { "DELETE", "DEL", ".D", "-", "REMOVE" };
        private List<string> searchSet = new List<string> { "SEARCH", ".S", "?", "FIND" };
        private List<string> editSet = new List<string> { "EDIT", ".E", "CHANGE", "MODIFY" };
        private List<string> clearSet = new List<string> { "CLEAR", "CLR", "--" };
        private List<string> homeSet = new List<string> { "HOME", ".H", "TASK", "LIST" };
        private List<string> postponeSet = new List<string> { "POSTPONE", ".P", "DELAY", "PP"};
        private List<string> doneSet = new List<string> { "DONE" };
        private List<string> undoSet = new List<string> { "UNDO" };
        private List<string> redoSet = new List<string> { "REDO" };
        //string lists for possible day of week conventional inputs
        private List<string> tomorrowSet = new List<string> { "TOMORROW", "TMRW", "TOM", "TMR" };
        private List<string> sundaySet = new List<string> { "SUN", "SUNDAY" };
        private List<string> mondaySet = new List<string> { "MON", "MONDAY" };
        private List<string> tuesdaySet = new List<string> { "TUE", "TUESDAY" };
        private List<string> wednesdaySet = new List<string> { "WED", "WEDNESDAY" };
        private List<string> thursdaySet = new List<string> { "THUR", "THURSDAY","THU" };
        private List<string> fridaySet = new List<string> { "FRI", "FRIDAY" };
        private List<string> saturdaySet = new List<string> { "SAT", "SATURDAY" };

        /// <summary>
        /// constructor for CommandParser class
        /// split the userCommand to a list
        /// create a command object
        /// </summary>
        /// <param name="_userCommand">command user input</param>

        public CommandParser(string userCommand)
        {
            Debug.Assert(userCommand != null);
            commandComponents = userCommand.Split(' ').ToList();
            for (int i = 0; i < commandComponents.Count && i>=0; i++)
            {
                if (commandComponents[i] == "")
                {
                    commandComponents.RemoveAt(i);
                    i--;
                }
            }
            command = new Command();
        }


        /// <summary>
        /// main function to analyze the command type and choose which way of command
        /// intepretation it should proceed to 
        /// </summary>
        private void AnalyseCommand()
        {
            if (commandComponents.Count == 0)
                return;
            string commandType = commandComponents[0].ToUpper();
            Debug.Assert(commandComponents[0] != null);

            if (addSet.Contains(commandType))
                ParseAddCommand();

            else if (deleteSet.Contains(commandType))
                ParseDeleteCommand();

            else if (searchSet.Contains(commandType))
                ParseSearchCommand();

            else if (editSet.Contains(commandType))
                ParseEditCommand();

            else if (clearSet.Contains(commandType))
                ParseClearCommand();

            else if (homeSet.Contains(commandType))
                ParseHomeCommand();

            else if (doneSet.Contains(commandType))
                ParseDoneCommand();

            else if (postponeSet.Contains(commandType))
                ParsePostponeCommand();
            else if (undoSet.Contains(commandType))
                ParseUndoCommand();
            else if (redoSet.Contains(commandType))
                ParseRedoCommand();
            //other command type are considered invalid
            else
            {
                
            }
        }




        /// <summary>
        /// main function for parsing add commands
        /// will read name location deadline from a string
        /// and store them into the command object
        /// words with "" are not considered reserved words
        /// </summary>
        /// 

        private bool ReadNamLocDea()
        {
            List<string> userTaskName = new List<string> { };
            List<string> userLocation = new List<string> { };
            List<string> userDeadline = new List<string> { };
            //read taskName first by default
            bool taskNameFlow = true;
            bool locationFlow = false;
            bool deadlineFlow = false;
            bool locked = false;
            for (int i = 0; i < commandComponents.Count; i++)
            {
                if (commandComponents[i] == "\"" || commandComponents[i][0] == '"'||commandComponents[i][commandComponents[i].Length-1]=='"')
                {
                    if (commandComponents[i] == "\"")
                    {
                        locked = !locked;
                        continue;
                    }
                    if (commandComponents[i][0] == '"')
                    {
                        locked = !locked;
                        commandComponents[i] = commandComponents[i].Remove(0, 1);
                    }
                    if (commandComponents[i][commandComponents[i].Length - 1] == '"')
                    {
                        locked = !locked;
                        commandComponents[i] = commandComponents[i].Remove(commandComponents[i].Length - 1, 1);
                    }

                }

                else if (locationSet.Contains(commandComponents[i].ToUpper()) && !locked)
                {
                    locationFlow = true;
                    taskNameFlow = false;
                    deadlineFlow = false;
                    continue;
                }
                else if (deadlineSet.Contains(commandComponents[i].ToUpper()) && !locked)
                {
                    deadlineFlow = true;
                    locationFlow = false;
                    taskNameFlow = false;
                    continue;
                }
                else ;
                if (taskNameFlow)
                    userTaskName.Add(commandComponents[i]);
                if (locationFlow)
                    userLocation.Add(commandComponents[i]);
                if (deadlineFlow)
                    userDeadline.Add(commandComponents[i]);
                Debug.Assert(taskNameFlow ^ locationFlow ^ deadlineFlow);
            }
            command.TaskName = string.Join(" ", userTaskName.ToArray());
            command.TaskLocation = string.Join(" ", userLocation.ToArray());
            SetDateTime(userDeadline);
            if (locked)
                return false;
            else
                return true;
        }
        private void ParseAddCommand()
        {   
            command.Type = "add";
            commandComponents.RemoveAt(0);
            ReadNamLocDea();
                
        }
        private void ParseRedoCommand()
        {
            command.Type = "redo";
        }

        private void ParseUndoCommand()
        {
            command.Type = "undo";
        }
        //*********************************************************************************************
        //******************************************************************
        //*********************************************************************************************
        /// <summary>
        /// helper function to set deadline by interpreting conventional date keywords
        /// basic formats are tommorrow/tmr/today/sunday/monday.../(11/11)/(Feb 2); + 12:12(am/pm)
        /// for weekdays : mon tue are considered nearest next mon/tue
        /// </summary>
        /// <param name="userDeadline">the list of datetime string</param>
        private bool SetDateTime(List<string> userDeadline)
        {
            DateTime temp;
            bool valid = true;
            int tem;
            for (int i = 1; i < userDeadline.Count; i++)
            {
                if ((userDeadline[i].ToUpper()=="AM" || userDeadline[i].ToUpper()=="PM") && int.TryParse(userDeadline[i - 1], out tem))
                {
                    userDeadline[i - 1] += userDeadline[i];
                    userDeadline.RemoveAt(i);
                }
            }
                if (userDeadline.Count == 1)
                {
                    int i;
                    if (userDeadline[0].ToUpper() == "TODAY")
                        command.TaskDeadline = DateTime.Today;

                    else if (tomorrowSet.Contains(userDeadline[0].ToUpper()))
                        command.TaskDeadline = DateTime.Today.AddDays(1);
                    // for now we just deal with date but not specific time

                    else if (sundaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Sunday);

                    else if (mondaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Monday);

                    else if (tuesdaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Tuesday);

                    else if (wednesdaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Wednesday);

                    else if (thursdaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Thursday);

                    else if (fridaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Friday);

                    else if (saturdaySet.Contains(userDeadline[0].ToUpper()))
                        SetDayOfWeek(DayOfWeek.Saturday);

                    else if (userDeadline[0].Contains("/"))
                    {

                        valid = DateTime.TryParse(userDeadline[0], out temp);
                        command.TaskDeadline = temp;
                    }
                    else if (userDeadline[0].Contains(":") || userDeadline[0].ToUpper().Contains("AM") || userDeadline[0].ToUpper().Contains("PM"))
                    {
                        valid = DateTime.TryParse(userDeadline[0], out temp);
                        command.TaskDeadline = temp;
                    }
                    else if (int.TryParse(userDeadline[0], out i))
                    {
                        valid = DateTime.TryParse(userDeadline[0] + ":00", out temp);
                        command.TaskDeadline = temp;
                    }
                    else
                        valid = false;

                }

                //
                else if (userDeadline.Count == 2)
                {

                    int i;
                    bool istime = false;
                    TimeSpan ts = new TimeSpan();

                    for (i = 0; i < userDeadline.Count; i++)
                    {
                        if (userDeadline[i].Contains(":") || userDeadline[i].ToUpper().Contains("AM") || userDeadline[i].ToUpper().Contains("PM"))
                        {
                            istime = true;
                            break;
                        }
                    }
                    if (istime)
                    {
                        valid = DateTime.TryParse(userDeadline[i], out temp);
                        ts = temp - DateTime.Today;
                        userDeadline.RemoveAt(i);
                        if (userDeadline[0].ToUpper() == "TODAY")
                            command.TaskDeadline = DateTime.Today;

                        else if (tomorrowSet.Contains(userDeadline[0].ToUpper()))
                            command.TaskDeadline = DateTime.Today.AddDays(1);
                        // for now we just deal with date but not specific time

                        else if (sundaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Sunday);

                        else if (mondaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Monday);

                        else if (tuesdaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Tuesday);

                        else if (wednesdaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Wednesday);

                        else if (thursdaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Thursday);

                        else if (fridaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Friday);

                        else if (saturdaySet.Contains(userDeadline[0].ToUpper()))
                            SetDayOfWeek(DayOfWeek.Saturday);

                        else if (userDeadline[0].Contains("/"))
                        {

                            valid = DateTime.TryParse(userDeadline[0], out temp);
                            command.TaskDeadline = temp;
                        }
                        else
                            valid = false;
                        command.TaskDeadline += ts;
                    }
                    else
                    {
                        valid = DateTime.TryParse(userDeadline[0] + " " + userDeadline[1], out temp);
                        command.TaskDeadline = temp;
                    }
                }
                else if (userDeadline.Count == 3)
                {
                    int i;
                    bool istime = false;
                    TimeSpan ts = new TimeSpan();

                    for (i = 0; i < userDeadline.Count; i++)
                    {
                        if (userDeadline[i].Contains(":") || userDeadline[i].ToUpper().Contains("AM") || userDeadline[i].ToUpper().Contains("PM"))
                        {
                            istime = true;
                            break;
                        }
                    }
                    if (istime)
                    {
                        int j;
                        valid = DateTime.TryParse(userDeadline[i], out temp);
                        ts = temp - DateTime.Today;
                        userDeadline.RemoveAt(i);
                        valid = DateTime.TryParse(userDeadline[0] + " " + userDeadline[1], out temp);
                        command.TaskDeadline = temp;
                        command.TaskDeadline += ts;
                    }
                    else
                        valid = false;
                }
                else
                    valid = false;
            if (!valid)
                command.TaskDeadline = DateTime.MinValue;
            return valid;

        }


       

        /// <summary>
        /// helper function to find the next monday/tuesday/etc.
        /// </summary>
        /// <param name="weekday">weekday like mon,tue...</param>
        private void SetDayOfWeek(DayOfWeek weekday)
        {
            DateTime deadline = DateTime.Today;

            while (deadline.DayOfWeek != weekday)
                deadline = deadline.AddDays(1);

            command.TaskDeadline = deadline;
        }


        /// <summary>
        /// main function for parsing delete command
        /// set command type to delete and regards the rest as taskindex or taskkey
        /// </summary>
        private void ParseDeleteCommand()
        {
            int index;

            command.Type = "delete";

            commandComponents.RemoveAt(0);
            for (int i = 0; i < commandComponents.Count; i++)
            {
                if (int.TryParse(commandComponents[i], out index))
                    command.TaskIndex.Add(index - 1);
                else
                    command.TaskKey.Add(commandComponents[i]);
            }
            RemoveDuplicateIndex();
        }

        /// <summary>
        /// set command type to search and the rest of the command will be regarded as taskkey
        /// </summary>
        private void ParseSearchCommand()
        {
            command.Type = "search";

            commandComponents.RemoveAt(0);

            command.TaskKey = commandComponents;
        }

        /// <summary>
        /// main function for pasing edit command
        /// first use search method to find the task and change its location or deadline
        /// </summary>
        private void ParseEditCommand()
        {
            int index;

            command.Type = "edit";

            commandComponents.RemoveAt(0);

            if (commandComponents.Count == 0)
                return;

            if (int.TryParse(commandComponents[0], out index))
            {
                command.TaskIndex.Add(index - 1);
                commandComponents.RemoveAt(0);
                ReadNamLocDea();
            }
            else
            {
                for (int j = 0; j < commandComponents.Count && !(deadlineSet.Contains(commandComponents[j].ToUpper())) && !(locationSet.Contains(commandComponents[j].ToUpper())); j++)
                    command.TaskKey.Add(commandComponents[j]);
            }
        }
        /// <summary>
        /// set the command type to clear
        /// </summary>
        private void ParseClearCommand()
        {
            command.Type = "clear";
        }
        /// <summary>
        /// set the command type to home
        /// </summary>
        private void ParseHomeCommand()
        {
            command.Type = "home";
        }
        /// <summary>
        /// set the command type to done
        /// </summary>
        private void ParseDoneCommand()
        {
            int index;

            command.Type = "done";

            commandComponents.RemoveAt(0);
            for (int i = 0; i < commandComponents.Count; i++)
            {
                if (int.TryParse(commandComponents[i], out index))
                    command.TaskIndex.Add(index - 1);
                else
                    command.TaskKey.Add(commandComponents[i]);
            }
            RemoveDuplicateIndex();
        }

        private void ParsePostponeCommand()
        {
            int index;

            command.Type = "postpone";

            commandComponents.RemoveAt(0);
            for (int i = 0; i < commandComponents.Count; i++)
            {
                if (int.TryParse(commandComponents[i], out index))
                    command.TaskIndex.Add(index - 1);
                else
                    command.TaskKey.Add(commandComponents[i]);
            }
            RemoveDuplicateIndex();
        }

        private void ParseInvalidCommand()
        {
            command.Type = "Invalid";
        }

        /// <summary>
        /// public function to return the fully analyzed command
        /// back to CommandControl
        /// </summary>
        /// <returns>return the command which command parser dealt with</returns>
        public Command GetCommand()
        {
            AnalyseCommand();
            return command;
        }

        /// <summary>
        /// Helper function
        /// </summary>
        private void RemoveDuplicateIndex()
        {
            command.TaskIndex.Sort();
            command.TaskIndex.Reverse();
            List<int> deleteIndex = new List<int> { };
            for (int i = 0; i < command.TaskIndex.Count; i++)
            {
                if (!deleteIndex.Contains(command.TaskIndex[i]))
                    deleteIndex.Add(command.TaskIndex[i]);
            }
            command.TaskIndex.Clear();
            command.TaskIndex = deleteIndex;
        }
    }
}
